#include <gfx/dx12/core.h>
#include <gfx/dx12/helper.h>
#include <utl/vector.h>

namespace primal
{
	namespace gfx
	{
		template<size_t S>
		struct BufferData
		{
			u8 data[S];
		};

		constexpr u64 constant_buffer_size_offset[]
		{
			(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT * 0ull),
			(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT * 1ull),
			(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT * 2ull),
			(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT * 3ull),
			(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT * 4ull),
			(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT * 5ull),
		};

		constexpr u64 multiple_buffer_count{ 4ull };

		template<typename T>
		u64 get_constant_align()
		{
			static u64 temp_cache{ DXCONSTANTALIGN(sizeof(T)) };
			return temp_cache;
		}

		template<typename T>
		u64 get_buffer_index()
		{
			static u64 temp_index{ sizeof(T) / static_cast<u64>(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT) };
			return temp_index;
		}

		struct ConstantBuffer
		{
			ID3D12Resource* res;
			void* cpu_address;
			D3D12_GPU_VIRTUAL_ADDRESS gpu_address;
			descriptor_handle handle;
			template<typename T>
			T& ref_data();
		};

		class ConstantBufferAllocator
		{
		public:
			ConstantBufferAllocator();
			template<typename T>
			ConstantBuffer* allocator();
			void reset();
		private:
			struct
			{
				utl::vector<ConstantBuffer> buf;
				u64 inx;
			}buffers[multiple_buffer_count];
		};

		/*
		inline ConstantBuffer::ConstantBuffer(u64 bs) :buffer_size{ bs }, res{}
		{
			d3dx::resource_desc desc{};
			desc.as_buffer(buffer_size);
			DXASSERT(device()->CreateCommittedResource(&d3dx::heap_properties.upload_heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&this->res)));
			this->gpu_address = res->GetGPUVirtualAddress();
			res->Map(0u, nullptr, &cpu_address);
		}

		inline ConstantBuffer::~ConstantBuffer()
		{
			if(res != nullptr)
			{
				res->Unmap(0u, nullptr);
				res->Release();
			}
		}
		*/

		/*inline D3D12_GPU_VIRTUAL_ADDRESS ConstantBuffer::get_gpu_address()
		{
			return this->gpu_address;
		}

		inline void ConstantBuffer::init_handle()
		{
			srv().allocator(handle);
			d3dx::view_desc vd{};
			device()->CreateConstantBufferView(vd.as_constant(gpu_address, buffer_size), handle.cpu);
		}

		inline D3D12_GPU_DESCRIPTOR_HANDLE ConstantBuffer::get_gpu_handle()
		{
			return handle.gpu;
		}*/

		inline ConstantBufferAllocator::ConstantBufferAllocator() : buffers{}
		{}

		template<typename T>
		inline ConstantBuffer* ConstantBufferAllocator::allocator()
		{
			static u64 temp_index{ sizeof(T) / static_cast<u64>(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT) };
			static u64 buffer_align_size{ DXCONSTANTALIGN(sizeof(T)) };

			auto& buffer{ buffers[temp_index] };
			ConstantBuffer* res{};
			if(buffer.inx >= buffer.buf.size())
			{
				buffer.buf.emplace_back();
				res = &(buffer.buf[buffer.inx]);
				d3dx::resource_desc desc{};
				desc.as_buffer(buffer_align_size);
				DXASSERT(device()->CreateCommittedResource(&d3dx::heap_properties.upload_heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&res->res)));
				res->gpu_address = res->res->GetGPUVirtualAddress();
				res->res->Map(0u, nullptr, &res->cpu_address);
			}
			else
			{
				res = &(buffer.buf[buffer.inx]);
			}
			++buffer.inx;
			return res;
		}

		inline void ConstantBufferAllocator::reset()
		{
			for(auto& item : buffers)
			{
				item.inx = 0;
			}
		}

		/*template<typename T>
		inline T& ConstantBuffer::data()
		{
			return *((T*)this->cpu_address);
		}*/

		template<typename T>
		inline T& ConstantBuffer::ref_data()
		{
			return *((T*)this->cpu_address);
		}

		/*template<u64 size_id>
		ConstantBufferAllocator<size_id>& get_constant_allocator()
		{
			static ConstantBufferAllocator<size_id> allocator{};
			return allocator;
		}*/
	}
}